package com.tceasy.gateway.api;

import com.tceasy.common.utils.date.DateUtils;
import com.tceasy.common.utils.json.JsonUtil;
import com.tceasy.common.utils.string.StringUtil;
import com.tceasy.gateway.Constants;
import com.tceasy.gateway.dao.ArchiveLogDao;
import com.tceasy.gateway.dao.DeveloperDao;
import com.tceasy.gateway.dao.ServiceDao;
import com.tceasy.gateway.domain.OpenArchiveLog;
import com.tceasy.gateway.domain.OpenDeveloper;
import com.tceasy.gateway.domain.OpenPermission;
import com.tceasy.gateway.domain.OpenService;
import com.tceasy.gateway.util.HttpClient;
import com.tceasy.util.SemaphoreThreadUtil;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 *Created by zhanghuan on 2016/4/10.
 */
@Component
public class RequestDispatcher {

    static final Logger logger= LoggerFactory.getLogger(RequestDispatcher.class);

    @Autowired
    DeveloperDao developerDao;
    @Autowired
    ServiceDao serviceDao;
    @Autowired
    ArchiveLogDao archiveLogDao;
    @Autowired
    StringRedisTemplate stringRedisTemplate;

    SemaphoreThreadUtil threadUtil = new SemaphoreThreadUtil();

    /**
     * 请求转发
     * @return
     */
    public Response dispatch(final Map request){
        Response response=null;
        try{
            response = process(request);
        } catch (Throwable throwable) {
            logger.error(throwable.getMessage(), throwable);
            response= Response.fail(throwable.getMessage());
        }
        return response;
    }

    /**
     * 保存请求日志
     * @param request 请求数据
     * @param response 返回数据
     */
    private void addArchiveLog(Map request, Map response) {
        logger.info("开始记录归档日志request:{},response:{}",request,response);
        Date startDate = new Date();
        String remoteIp= (String) request.get("remoteIp");
        String requestId= (String) request.get("requestId");
        OpenArchiveLog archiveLog=new OpenArchiveLog(request,response);
        Date endDate=new Date();
        archiveLog.setStartDate(startDate);
        archiveLog.setEndDate(endDate);
        archiveLog.setProcessTime(endDate.getTime() - startDate.getTime());
        archiveLog.setRequestId(requestId);
        archiveLog.setRemoteIp(remoteIp);
        archiveLogDao.insert(archiveLog);
    }

    /**
     * 处理请求过程
     * @param request 客户端的请求参数
     * @return
     */
    private Response process(Map request) {
        String serviceStr= (String) request.get("service");/*请求接口*/
        String partner= (String) request.get("partner");/*开发者账号*/
        String remoteIp= (String) request.get("remoteIp");/*访问ip*/
        OpenDeveloper developer=developerDao.findOneByPk(partner);
        OpenService service=serviceDao.findOneByPk(serviceStr);
        Assert.notNull(service,"接口不存在");
        Assert.notNull(developer,"开发者账号不存在");
        Assert.isTrue(StringUtils.equals("Y",service.getState()),"接口已关闭");
        OpenPermission permission=developerDao.getPermission(partner,serviceStr);
        List<String> ips=developer.getIps();
        /*ip白名单非空，判断是否在白名单中*/
        logger.info("白名单ips:{}访问者ip:{}", ips, remoteIp);
        Assert.isTrue(CollectionUtils.isEmpty(ips) || ips.contains(remoteIp),"ip地址不允许访问");
        /*校验是否到达访问次数限制*/
        checkOrUpdateLimit(permission,partner,LimitOperatorType.CHECK);
        /*增加扩展参数*/
        request.putAll(developer.getExtParam());
        /*p1=${p1}&json={id:${id},value:${value}}*/
        String paramTemplate=service.getParamTemplate();
        /*替换占位符*/
        String url= RequestParamUtil.replacePlaceholders(service.getUrl(), request);
        String param=RequestParamUtil.replacePlaceholders(paramTemplate,request);
        param=param.replaceAll("\\$\\{\\w*\\}","");/*可选参数去除多余占位符*/
        String encode="utf-8";
        String result;
        if(StringUtils.equalsIgnoreCase(service.getRequestMethod(),"POST")){
            result= HttpClient.sendRequestByPostGiveContentType(url, param, true, service.getContentType(), encode);
        }else{
            String urlAndParam=new StringBuilder(url).append(StringUtils.isBlank(param)?"":"?"+param).toString();
            result=HttpClient.sendRequestByGet(urlAndParam,service.getContentType(),true,encode);
        }
        Map retMap= JsonUtil.toMap(result);
        Assert.notNull(retMap, "系统错误");
        /*更新访问记录*/
        checkOrUpdateLimit(permission,partner,LimitOperatorType.UPDATE);
        return Response.success().putAll(retMap);
    }

    /**
     * 限制访问校验或者更新访问记录
     * @param permission
     * @param partner
     * @param type 操作类型
     */
    private void checkOrUpdateLimit(OpenPermission permission,String partner,LimitOperatorType type){
        logger.info("校验是否到达访问限制次数，permission:{},partner:{}",permission,partner);
        Assert.notNull(permission,"无权访问");
        String method=permission.getMethod();
        String keyPrefx= StringUtil.join(":", Constants.redisPrefx,partner, method);
        String second= DateUtils.currentYyyyMMddHHmmss();
        String minute=second.substring(0,12);
        String hour=second.substring(0,10);
        String day=second.substring(0,8);
        long limitS=permission.getLimitS();
        long limitM=permission.getLimitM();
        long limitH=permission.getLimitH();
        long limitD=permission.getLimitD();
        if(limitS>-1){
            String key=keyPrefx+":"+second;
            if(type==LimitOperatorType.CHECK){
                checkLimit(limitS,key,"每秒钟限制访问"+limitS+"次");
            }else{
                updateLimit(key,TimeUnit.SECONDS);
            }
        }
        if(limitM>-1){
            String key=keyPrefx+":"+minute;
            if(type==LimitOperatorType.CHECK){
                checkLimit(limitM,key,"每分钟限制访问"+limitM+"次");
            }else{
                updateLimit(key,TimeUnit.MINUTES);
            }
        }
        if(limitH>-1){
            String key=keyPrefx+":"+hour;
            if(type==LimitOperatorType.CHECK){
                checkLimit(limitH,key,"每小时限制访问"+limitH+"次");
            }else{
                updateLimit(key,TimeUnit.HOURS);
            }
        }
        if(limitD>-1){
            String key=keyPrefx+":"+day;
            if(type==LimitOperatorType.CHECK){
                checkLimit(limitD,key,"每天限制访问"+limitD+"次");
            }else{
                updateLimit(key,TimeUnit.DAYS);
            }
        }
    }


    /**
     * 校验是否超出访问限制
     * @param limit 限制次数
     * @param key 缓存key
     * @param msg 超出限制的提示消息
     */
    private void checkLimit(long limit,String key,String msg) {
        BoundValueOperations<String,String> operations=stringRedisTemplate.boundValueOps(key);
        String times=operations.get();
        if(StringUtils.isNotBlank(times)){
            long timesLongVal=Long.parseLong(times);
            logger.info("访问接口的key：{},访问次数：{}",key,times);
            Assert.isTrue(timesLongVal < limit,msg);
        }
    }

    /**
     * 更新访问记录，初始化时设置过期时间为2个单位（例如2秒或者2分钟）
     * @param key 缓存key
     * @param timeUnit 过期时间单位
     */
    private void updateLimit(String key,TimeUnit timeUnit){
        BoundValueOperations<String,String> operations=stringRedisTemplate.boundValueOps(key);
        String times=operations.get();
        /*访问次数加1，如果值为空则初始化并设置过期时间2个单位*/
        if(StringUtils.isBlank(times)){
            operations.increment(1);
            operations.expire(2,timeUnit);
        }else {
            operations.increment(1);
        }
    }

    /**
     * 访问限制操作类型
     * CHECK：访问限制检查
     * UPDATE:更新访问记录
     */
    enum LimitOperatorType{
        CHECK,UPDATE
    }

}
